#if !defined(ximera_system_application_hpp)
#define ximera_system_application_hpp

// -----------------------------------------------------------

#include <ximera/system/appsettings.hpp>
#include <ximera/system/apphandler.hpp>
#include <ximera/system/pluginmanager.hpp>
#include <ximera/system/timer.hpp>
#include <ximera/threading/messagequeue.hpp>
#include <ximera/common/signal.hpp>

#include <ximera/graphics/igraphicssystem.hpp>
#include <ximera/input/iinputsystem.hpp>
#include <ximera/audio/iaudiosystem.hpp>
#include <ximera/scripting/iscriptingsystem.hpp>
#include <ximera/gui/iguisystem.hpp>
#include <ximera/physics/iphysicssystem.hpp>

namespace ximera { namespace system
{
    //! \brief Represents an application which provides the game loop
    //!
    //! This class assists in the loading of the system plugins and contains
    //! a predefined game loop that is optional. An example of using it is:
    //! \code
    //! int main(int argc, char* argv)
    //! {
    //!     system::AppSettings settings(argc, argv);
    //!     settings[L"ximera.plugins.input"] = L"XimeraDInput8";
    //!
    //!     system::Application app(settings);
    //!     return app.run();
    //! }
    //! \endcode
    class XIMERA_API Application
    {
    public:
        //! The event type that's called when the application is first started
        typedef void (OnStartEvent)(AppEventArgs& e);
        //! The event type that's called after the application updates
        //! \param secs Indicates how many seconds have passed since the last update
        typedef void (OnUpdateEvent)(UpdateEventArgs& e);
        //! The event type that's called before the application renders the scene
        typedef void (OnRenderEvent)(AppEventArgs& e);
        //! The event type that's called when a close request has been placed
        //! \param allowClose Set to false to disallow closing
        typedef void (OnCloseRequestEvent)(CloseRequestEventArgs& e);
        //! The event type that's called when the application has been closed
        typedef void (OnCloseEvent)(AppEventArgs& e);

    public:
        Application();
        Application(AppSettings const& settings);

        //! Returns a reference to this application's settings
        AppSettings& settings();
        AppSettings const& settings() const;

        graphics::IGraphicsSystem* graphicsSystem();
        input::IInputSystem* inputSystem();
        audio::IAudioSystem* audioSystem();
        scripting::IScriptingSystem* scriptingSystem();
        gui::IGuiSystem* guiSystem();
        physics::IPhysicsSystem* physicsSystem();

        //! \brief Allows the application to process its messages
        //!
        //! Calling this method allows the application to process all messages currently
        //! in its queue. This method is called automatically by run().
        void processMessages();
        
        //! \brief Updates the application using its internal timer
        //!
        //! Updates things like the input system and it will call the onUpdate handlers. 
        //! This method is called automatically by run().
        void update();

        //! \brief Updates the application with the amount of seconds passed
        //! Updates things like the input system and it will call the onUpdate handlers. 
        //! \param secs The seconds used to update the application      
        void update(Real secs);

        //! Renders the current frame
        void render();

        //! \brief Runs the application
        //! This is a convenient method that runs the application automatically. You can
        //! of course by-pass this method and have your own game loop by calling the
        //! appropriate functions.
        int run();

        //! \brief Queues a message in this application's message queue.
        //! It will be processed the next time processMessages is called.
        //! \param msg The message to queue
        void sendMessage(threading::IMessage* msg);

        //! Indicates whether someone has requested this application to close.
        bool shouldClose() const;
        //! \brief Requests the application to close.
        //! Calls the onCloseRequest event.
        void shouldClose(bool close);

        //! Returns the frames-per-second the application is running.
        Real fps() const;

        //! Event that is raised when the application has started.
        SignalAccess<OnStartEvent> onStart();
        //! Event that is raised when the application has updated.
        SignalAccess<OnUpdateEvent> onUpdate();
        //! Event that is raised before the frame is rendered.
        SignalAccess<OnRenderEvent> onBeforeRender();
        //! Event that is raised after the frame is rendered.
        SignalAccess<OnRenderEvent> onAfterRender();
        //! Event that is raised when someone has requested the application to close.
        SignalAccess<OnCloseRequestEvent> onCloseRequest();
        //! Event that is raised when the application has closed.
        SignalAccess<OnCloseEvent> onClose();

        //! Adds an event handler
        void addHandler(AppHandler& handler);
        //! Removes an event handler
        void removeHandler(AppHandler& handler);

    private:
        AppSettings _settings;
        
        Timer _timer;
        bool _shouldClose;
        Real _fps;

        threading::MessageQueue _messageQueue;
        PluginManager _plugins;

        SharedPtr<graphics::IGraphicsSystem> _graphicsSystem;
        SharedPtr<input::IInputSystem> _inputSystem;
        SharedPtr<audio::IAudioSystem> _audioSystem;
        SharedPtr<scripting::IScriptingSystem> _scriptingSystem;
        SharedPtr<gui::IGuiSystem> _guiSystem;
        SharedPtr<physics::IPhysicsSystem> _physicsSystem;

        Signal<OnStartEvent> _onStart;
        Signal<OnUpdateEvent> _onUpdate;
        Signal<OnRenderEvent> _onBeforeRender;
        Signal<OnRenderEvent> _onAfterRender;
        Signal<OnCloseRequestEvent> _onCloseRequest;
        Signal<OnCloseEvent> _onClose;

        void init();
    };
}}

// -----------------------------------------------------------

#endif